#include "properties.hpp"
#include "parser.hpp"
#include "vartab.hpp"

// Properties member functions.

Properties::Properties() {
    code = "";
    value = "";
    type = NULL;
    constant = false;
}

Properties::Properties(string c, string v, Type* t, bool b) {
    code = c;
    value = v;
    type = t;
    constant = b;
}

void Properties::check() {
    if (!constant) {
        Properties *correct = SymbolTable::checkvar(value);
        value = correct->value;
        code = correct->code;
        type = correct->type;
        constant = correct->constant;
    }
}

void Properties::check(Properties& var) {
    var.check();
    code = var.code;
    value = var.value;
    type = var.type;
    constant = var.constant;
}

void Properties::program(Properties& defs, Properties& code) {
    cout << "#include <stdlib.h>" << endl
            << "#include <string.h>" << endl
            << "#include <iostream>" << endl
            << "using namespace std;" << endl
            << SymbolTable::allcdecs() << endl
            << defs.code
            << "int main() {" << endl
            << code.code
            << "}\n";
}

void Properties::concat(Properties& code1, Properties& code2) {
    code = code1.code + code2.code;
}

void Properties::assignment(Properties &id, Properties& exp) {
    code = exp.code;
    type = Operation::binop(id, exp, OP_ASG);
    if (arrayassignment) {
        code += HomoList::code(id.value);
        arrayassignment = false;
    } else {
        code += id.value + " = " + exp.value + ";\n";
    }
}

void Properties::arrayassign(Properties& array, Properties& index, Properties & exp) {
    type = array.type->undertype();
    type = Operation::binop(*this, exp, OP_ASG);
    if (type == NULL)
        Error::inctypesassign(array, exp);
    value = array.type->cderef(array.value, index.value);
    code = exp.code + index.code + value + " = " + exp.value + ";\n";
}

void Properties::swapping(Properties& var1, Properties& var2) {
    if (Operation::binop(var1, var2, OP_ASG) !=
        Operation::binop(var1, var2, OP_ASG))
        Error::swapwrong(var1, var2);
    string aux = SymbolTable::swapaux(var1.type);
    code = var1.code + var2.code +
            aux + " = " + var1.value + ";\n" +
            var1.value + " = " + var2.value + ";\n" +
            var2.value + " = " + aux + ";\n";
}

void Properties::operation(Properties& opl, Properties& opr, Operator ops) {
    type = Operation::binop(opl, opr, ops);
    value = SymbolTable::newtemp(type);
    code = opl.code + opr.code + value + " = " + opl.value +
            " " + Operation::opstr(ops) + " " + opr.value + ";\n";
}

void Properties::arrayitem(Properties& array, Properties& index) {
    if (array.type->simple())
        Error::notarray(array, index);
    if (!index.type->integer())
        Error::notindex(array, index);
    type = array.type->undertype();
    value = SymbolTable::newtemp(type);
    code = array.code + index.code + value + " = " +
            array.type->cderef(array.value, index.value) + ";\n";
}

void Properties::ifthen(Properties& test, Properties& comand) {
    if (!test.type->logic())
        Error::nonlogictest(test);
    int lindex = Labels::newiflabel();
    string then = Labels::then(lindex);
    string endif = Labels::endif(lindex);
    code = test.code + "if(" + test.value + ") goto " + then + ";\n"
            + "goto " + endif + ";\n"
            + then + ":\n"
            + comand.code
            + endif + ":\n";
}

void Properties::ifthenelse(Properties& test, Properties& com, Properties& alt) {
    if (!test.type->logic())
        Error::nonlogictest(test);
    int lindex = Labels::newiflabel();
    string then = Labels::then(lindex);
    string endif = Labels::endif(lindex);
    code = test.code + "if(" + test.value + ") goto " + then + ";\n"
            + alt.code + "goto " + endif + ";\n"
            + then + ":\n"
            + com.code
            + endif + ":\n";
}

void Properties::iterate(Properties& index, Properties& a, Properties& b, Properties& comand) {
    if (!index.type->integer())
        Error::nonintindex(index);
    if (!a.type->integer() || !b.type->numeric())
        Error::nonnumericl(a, b);
    int num = Labels::newforlabel();
    string temp = SymbolTable::newtemp(Logic::type());
    string fortest = Labels::fortest(num);
    string forexit = Labels::forexit(num);
    code = a.code + b.code + index.value + " = " + a.value + ";\n";
    code += fortest + ":\n"
            + temp + " = " + index.value + " > " + b.value + ";\n";
    code += "if(" + temp + ") goto " + forexit + ";\n";
    code += comand.code;
    code += index.value + " = " + index.value + " + 1;\n";
    code += "goto " + fortest + ";\n" + forexit + ":\n";
}

void Properties::ifloop(Properties& test, Properties& comand) {
    if (!test.type->logic())
        Error::nonlogictest(test);
    int num = Labels::newlooplabel();
    string looptest = Labels::looptest(num);
    string loopexit = Labels::loopexit(num);
    code = looptest + ":\n" + test.code + test.value
            + " = ! " + test.value + ";\n";
    code += "if(" + test.value + ") goto " + loopexit + ";\n";
    code += comand.code;
    code += "goto " + looptest + ";\n" + loopexit + ":\n";
}

void Properties::loopif(Properties& test, Properties& comand) {
    if (!test.type->logic())
        Error::nonlogictest(test);
    int num = Labels::newlooplabel();
    string loopbody = Labels::loopbody(num);
    string loopexit = Labels::loopexit(num);
    code = loopbody + ":\n" + comand.code + test.code;
    code += "if(" + test.value + ") goto " + loopbody + ";\n";
    code += loopexit + ":\n";
}

void Properties::print(Properties& var) {
    if (var.type->complex())
        Error::printcomplex(var);
    code = var.code + "cout << " + var.value + ";\ncout << endl;\n";
}

void Properties::scan(Properties& var) {
    // TODO Essa verificação de constante pode ser removida se um $1.check() for
    // inserido na regra sintática de print, pois isso forçará que a variável
    // esteja declarada.
    if (var.constant)
        Error::scantoconst(var);
    if (var.type->complex())
        Error::scancomplex(var);
    code = var.code + "cin >> " + var.value + ";\n";
}

void Properties::negatedterm(Properties& id, Operator negop) {
    type = Operation::unop(id, negop);
    if (type == NULL)
        Error::negundef(id, "-");
    value = SymbolTable::newtemp(type);
    switch (negop) {
        case OP_SUB:
            code = id.code + value + " = 0 - " + id.value + ";\n";
            break;
        case OP_NEG:
            code = id.code + value + " = ! " + id.value + ";\n";
            break;
        default:
            Error::panic(__FILE__, __LINE__, "Operador fornecido não é de negação");
    }
}

void Properties::returner(Properties& exp) {
    code = exp.code + Scope::returner() + " = " +
            exp.value + ";\ngoto " + Labels::functionexit(Scope::level()) + ";\n";
}